#include <config.h>
#include <epan/packet.h>

#define NHNS_PORT 50012

static int proto_nhns = -1;
static gint ett_nhns = -1;

//Packet field ints
static int hf_nhns_message_count = -1;
static int hf_nhns_message_code = -1;
static int hf_nhns_server_time = -1;
static int hf_nhns_index = -1;
static int hf_nhns_x = -1;
static int hf_nhns_y = -1;
static int hf_nhns_direction = -1;
static int hf_nhns_chat = -1;
static int hf_nhns_script = -1;


//Message codes copied from Message.hpp
enum MessageCode {
    m_b_ServerInfoRequest,
    m_c_ClockSyncReq,
    m_s_ClockSyncResp,
    m_c_ConnectionRequest,
    m_s_ConnectionRefuse,
    m_s_ConnectionTerminate,
    m_s_ConnectionAccept,
    m_c_LoadingComplete,
    m_s_ClientAdd,
    m_b_ChatMessage,
    m_c_SenderInfo,
    m_c_PlayerMove,
    m_s_PlayerMove,
    m_s_EntityMove,
    m_c_KeepAlive,
    m_s_GameState,
    m_s_NewClient,
    m_s_ClientDropped,
    m_s_Rename,
    m_s_NewEntity,
    m_s_DestroyEntity,
    m_NUM_MESSAGES
};

//Code names
static const value_string nhns_code_names[] = {
    { m_b_ServerInfoRequest, "m_b_ServerInfoRequest" },
    { m_c_ClockSyncReq, "m_c_ClockSyncReq" },
    { m_s_ClockSyncResp, "m_s_ClockSyncResp" },
    { m_c_ConnectionRequest, "m_c_ConnectionRequest" },
    { m_s_ConnectionRefuse, "m_s_ConnectionRefuse" },
    { m_s_ConnectionTerminate, "m_s_ConnectionTerminate" },
    { m_s_ConnectionAccept, "m_s_ConnectionAccept" },
    { m_c_LoadingComplete, "m_c_LoadingComplete" },
    { m_s_ClientAdd, "m_s_ClientAdd" },
    { m_b_ChatMessage, "m_b_ChatMessage" },
    { m_c_SenderInfo, "m_c_SenderInfo" },
    { m_c_PlayerMove, "m_c_PlayerMove" },
    { m_s_PlayerMove, "m_s_PlayerMove" },
    { m_s_EntityMove, "m_s_EntityMove" },
    { m_c_KeepAlive, "m_c_KeepAlive" },
    { m_s_GameState, "m_s_GameState" },
    { m_s_NewClient, "m_s_NewClient" },
    { m_s_ClientDropped, "m_s_ClientDropped" },
    { m_s_Rename, "m_s_Rename" },
    { m_s_NewEntity, "m_s_NewEntity" },
    { m_s_DestroyEntity, "m_s_DestroyEntity" }
};

static const value_string nhns_directions[] = {
    { 0, "None" },
    { 1, "dir_N" },
    { 2, "dir_E" },
    { 4, "dir_S" },
    { 8, "dir_W" },
    { 3, "dir_N && dir_E" },
    { 5, "dir_N && dir_S" },
    { 9, "dir_N && dir_W" },
    { 6, "dir_E && dir_S" },
    { 10, "dir_E && dir_W" },
    { 12, "dir_W && dir_S" }
};

static int dissect_nhns(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) {
    gint offset = 0;
    guint8 cmdtype = tvb_get_guint8(tvb, 0);

    col_set_str(pinfo-> cinfo, COL_PROTOCOL, "nhns");
    col_clear(pinfo->cinfo, COL_INFO);

    if (tree) {
        proto_item *ti = NULL;
        proto_item *nhns_tree = NULL;

        ti = proto_tree_add_item(tree, proto_nhns, tvb, 0, -1, ENC_NA);
        nhns_tree = proto_item_add_subtree(ti, ett_nhns);
        
        //Get message count
        proto_tree_add_item(nhns_tree, hf_nhns_message_count, tvb, offset, 2, ENC_LITTLE_ENDIAN);
        guint16 message_count = 0;
        message_count = tvb_get_letohs(tvb, offset);
        offset += 2;
        
        //Update info column
        col_add_fstr(pinfo->cinfo, COL_INFO, "Message Count: %d", message_count);
        
        //Loop over all messages
        int i = 0;
        for(i = 0; i < message_count; i += 1){
            
            //Get message code
            proto_tree_add_item(nhns_tree, hf_nhns_message_code, tvb, offset, 1, ENC_LITTLE_ENDIAN);
            guint8 code = tvb_get_guint8(tvb, offset);
            offset += 1;
            
            if(code >= m_NUM_MESSAGES){
                continue;
            }
            
            //Get server time.
            proto_tree_add_item(nhns_tree, hf_nhns_server_time, tvb, offset, 4, ENC_LITTLE_ENDIAN);
            offset += 4;
            
            if(code == m_s_EntityMove || code == m_s_PlayerMove){
                proto_tree_add_item(nhns_tree, hf_nhns_index, tvb, offset, 2, ENC_LITTLE_ENDIAN);//short client;
                offset += 2;
                proto_tree_add_item(nhns_tree, hf_nhns_direction, tvb, offset, 1, ENC_LITTLE_ENDIAN);//byte direction;
                offset += 1;
                proto_tree_add_item(nhns_tree, hf_nhns_x, tvb, offset, 4, ENC_LITTLE_ENDIAN);//float x;
                offset += 4;
                proto_tree_add_item(nhns_tree, hf_nhns_y, tvb, offset, 4, ENC_LITTLE_ENDIAN);//float y;
                offset += 4;
            }else if(code == m_c_SenderInfo){
                proto_tree_add_item(nhns_tree, hf_nhns_index, tvb, offset, 1, ENC_LITTLE_ENDIAN);
                guint8 client = tvb_get_guint8(tvb, offset);
                col_append_fstr(pinfo->cinfo, COL_INFO, ", From Client: %d", client);
                offset += 1;
                
            }else if(code == m_b_ChatMessage){
                proto_tree_add_item(nhns_tree, hf_nhns_index, tvb, offset, 1, ENC_LITTLE_ENDIAN);//byte client;
                offset += 1;
                proto_tree_add_item(nhns_tree, hf_nhns_chat, tvb, offset, 61, ENC_LITTLE_ENDIAN);//char[61] chat message
                
                gchar* chat_message = tvb_get_string(tvb, offset, 61);
                col_append_fstr(pinfo->cinfo, COL_INFO, ", Chat: \"%s\"", chat_message);
                offset += 61;
            
            }else if(code == m_c_PlayerMove){
                proto_tree_add_item(nhns_tree, hf_nhns_direction, tvb, offset, 1, ENC_LITTLE_ENDIAN);
                offset += 1;
            }else if(code == m_s_NewEntity){
                proto_tree_add_item(nhns_tree, hf_nhns_direction, tvb, offset, 1, ENC_LITTLE_ENDIAN);//byte direction;
                offset += 1;
                proto_tree_add_item(nhns_tree, hf_nhns_index, tvb, offset, 2, ENC_LITTLE_ENDIAN);//short client;
                offset += 2;

                proto_tree_add_item(nhns_tree, hf_nhns_script, tvb, offset, 10, ENC_LITTLE_ENDIAN);//char[10] script name
                offset += 10;
                
                proto_tree_add_item(nhns_tree, hf_nhns_x, tvb, offset, 4, ENC_LITTLE_ENDIAN);//float x;
                offset += 4;
                proto_tree_add_item(nhns_tree, hf_nhns_y, tvb, offset, 4, ENC_LITTLE_ENDIAN);//float y;
                offset += 4;
            }
             
        }
        return offset;
    }
    return tvb_length(tvb);
}

void proto_register_nhns(void) {

    static hf_register_info hf_nhns[] = {
        { 
            &hf_nhns_message_count,
            { "Message Count", "nhns.message_count",
                FT_UINT16, BASE_DEC,
                NULL, 0x0,
                NULL, HFILL}
         },
         {
            &hf_nhns_message_code,
            { "Message Code", "nhns.message_code",
                FT_UINT8, BASE_DEC,
                nhns_code_names, 0x0,
                NULL, HFILL}
         },
         {
            &hf_nhns_server_time,
            { "Server Time", "nhns.server_time",
                FT_UINT32, BASE_DEC,
                NULL, 0x0,
                NULL, HFILL}
         },
         {
            &hf_nhns_index,
            { "Index", "nhns.index",
                FT_UINT8, BASE_DEC,
                NULL, 0x0,
                NULL, HFILL}
         },
         {
            &hf_nhns_x,
            { "X", "nhns.x",
                FT_FLOAT, BASE_DEC,
                NULL, 0x0,
                NULL, HFILL}
         },
         {
            &hf_nhns_y,
            { "Y", "nhns.y",
                FT_FLOAT, BASE_DEC,
                NULL, 0x0,
                NULL, HFILL}
         },
         {
            &hf_nhns_direction,
            { "Direction", "nhns.direction",
                FT_UINT8, BASE_DEC,
                nhns_directions, 0x0,
                NULL, HFILL}
         },
         {
            &hf_nhns_chat,
            { "Chat Message", "nhns.chat",
                FT_STRING, BASE_DEC,
                NULL, 0x0,
                NULL, HFILL}
         },
         {
            &hf_nhns_script,
            { "Script", "nhns.script",
                FT_STRING, BASE_DEC,
                NULL, 0x0,
                NULL, HFILL}
         }
         
    };

    static gint * ett_nhns_arr[] = {
        &ett_nhns
    };

    proto_nhns = proto_register_protocol("nhns client-server protocol", "nhns", "nhns");
    proto_register_field_array(proto_nhns, hf_nhns, array_length(hf_nhns));
    proto_register_subtree_array(ett_nhns_arr, array_length(ett_nhns_arr));
}

void proto_reg_handoff_nhns_nhns(void) {
    static dissector_handle_t nhns_handle;
    nhns_handle = new_create_dissector_handle(dissect_nhns, proto_nhns);
    dissector_add("udp.port", NHNS_PORT, nhns_handle);
}

